#include "model_mesh.h"
#include <widgets/palette_widget.h> // Palette_widget::load_stops
#include <boost/bind.hpp>
#include <util/normalizator.h>
#include <util/gradient_interpolator.h>

namespace models {

  //
  // models::Mesh::Frame
  //

  Mesh::Frame::Frame(Geometry::SP p_geometry, QVector<Scalar_field::SP> param)
    :p_geometry_(p_geometry),
    param_(param)
  {
  }

  Mesh::Scalar_field::SP Mesh::Frame::get_param(const QString & _Name) const
  {
    for (int i = 0; i < get_param_qty(); ++i)
      if (get_param(i)->get_name() == _Name)
        return get_param(i);

    return Scalar_field::SP();
  }

  void Mesh::Frame::set_element_field(IMesh::element_type type, QVector<Scalar_field::SP> param)
  {
    element_param_[type] = param;
  }
  
  QVector<Mesh::Scalar_field::SP> Mesh::Frame::get_element_field(IMesh::element_type type)
  {
    return element_param_[type];
  }

  
  size_t Mesh::Frame::get_element_param_qty(IMesh::element_type type) const
  {
    if (!element_param_.contains(type))
      return 0;
      
    return element_param_[type].size();
  }
  
  Mesh::Scalar_field::SP Mesh::Frame::get_element_param(IMesh::element_type type, size_t ind_param) const
  {
    if (!element_param_.contains(type))
      return Scalar_field::SP();
      
    return element_param_[type][ind_param];
  }

  Mesh::Scalar_field::SP Mesh::Frame::get_element_param(IMesh::element_type type, const QString & _Name) const
  {
    if (!element_param_.contains(type))
      return Scalar_field::SP();
    
    for (int i = 0; i < get_element_param_qty(type); ++i)
      if (get_element_param(type, i)->get_name() == _Name)
        return get_element_param(type, i);

    return Scalar_field::SP();
  }

  //
  // models::Mesh::Memory_reader_writer
  //
  size_t Mesh::Memory_reader_writer::get_frame_qty() const
  {
    return frames_.size();
  }

  void Mesh::Memory_reader_writer::write(Frame::SP p_frame)
  {
    frames_.push_back(p_frame);
  }

  Mesh::Frame::SP Mesh::Memory_reader_writer::do_read(size_t frame)
  {
    return frames_[frame];
  }

  //
  // models::Mesh
  //
  Mesh::Mesh(QObject *parent)
    :QObject(parent)
  {
  }

  Mesh::Mesh(Frame_reader::SP p_reader, QObject *parent)
    : QObject(parent),
    current_frame_(0),
    scalar_field_(0),
    p_frame_reader_(boost::dynamic_pointer_cast<Frame_random_access_reader>(p_reader)),
    p_scalar_field_menu_(0)
  {
    gradient_stops_ <<
      QGradientStop(0.0,       Qt::blue)   <<
      QGradientStop(1.0 / 4.0, Qt::cyan)   <<
      QGradientStop(2.0 / 4.0, Qt::green)  <<
      QGradientStop(3.0 / 4.0, Qt::yellow) <<
      QGradientStop(1.0,       Qt::red);
      
    if (!p_frame_reader_)
    {
      Memory_reader_writer *p_memory_reader_writer = new Memory_reader_writer;
      copy_frames(p_reader.get(), p_memory_reader_writer);
      p_frame_reader_.reset(p_memory_reader_writer);
    }
    set_current_frame(0);
    QObject::connect(&timer_, SIGNAL(timeout()), this, SLOT(on_timer()));
  }

  Mesh::~Mesh()
  {
    if (p_scalar_field_menu_)
      p_scalar_field_menu_->deleteLater();
  }

  size_t Mesh::get_frame_qty() const
  {
    return p_frame_reader_->get_frame_qty();
  }

  size_t Mesh::get_current_frame() const
  {
    return current_frame_;
  }

  void Mesh::set_current_frame(size_t frame)
  {
    using namespace boost;
    current_frame_ = frame;
    p_current_frame_ = p_frame_reader_->read(frame);

    { // vertex scalar fields
      const int scalar_field_qty = p_current_frame_->get_param_qty();
      vertices_color_.resize(scalar_field_qty);

      for (int i = 0; i < scalar_field_qty; ++i)
      {
        Scalar_field::SP p_scalar_field = p_current_frame_->get_param(i);

        const QVector<qreal> &values = p_scalar_field->get_values();
        vertices_color_[i].resize(values.size());
        util::Normalizator normalizator(p_scalar_field->get_min(), p_scalar_field->get_max());
        util::GradientInterpolator gradient_interpolator(gradient_stops_);
        std::transform(values.begin(), values.end(), vertices_color_[i].begin(), bind(gradient_interpolator, bind(normalizator, _1)));
      }
    }

    { // element scalar fields
      for (int t = 0; t < IMesh::element_qty; t++)
      {
        IMesh::element_type type = IMesh::element_type(t);
        
        const int scalar_field_qty = p_current_frame_->get_element_param_qty(type);
        elements_color_[type].resize(scalar_field_qty);

        for (int i = 0; i < scalar_field_qty; ++i)
        {
          Scalar_field::SP p_scalar_field = p_current_frame_->get_element_param(type, i);

          const QVector<qreal> &values = p_scalar_field->get_values();
          elements_color_[type][i].resize(values.size());
          util::Normalizator normalizator(p_scalar_field->get_min(), p_scalar_field->get_max());
          util::GradientInterpolator gradient_interpolator(gradient_stops_);
          std::transform(values.begin(), values.end(), elements_color_[type][i].begin(), bind(gradient_interpolator, bind(normalizator, _1)));
        }
      }
    }
    
    emit changed();
  }

  QGradientStops Mesh::get_gradient_stops() const
  {
    return gradient_stops_;
  }

  Mesh::Frame::SP Mesh::get_frame()
  {
    return p_current_frame_;
  }

  void Mesh::set_current_scalar_field(int index)
  {
    scalar_field_ = index;
    set_current_frame(get_current_frame());
  }

  size_t Mesh::get_current_scalar_field() const
  {
    return (scalar_field_ < p_current_frame_->get_param_qty()) ? scalar_field_ : size_t(-1);
  }

  void Mesh::set_gradient_stops(const QGradientStops &stops)
  {
    gradient_stops_ = stops;
    set_current_frame(get_current_frame());
  }


  void Mesh::on_timer()
  {
    set_current_frame((current_frame_ + 1) % get_frame_qty());
  }

  const IMesh::t_container_vertex &Mesh::get_vertices() const
  {
    return p_current_frame_->get_geometry()->get_vertices();
  }

  const IMesh::t_container_color &Mesh::get_vertices_color(const QString &name) const
  {
    const size_t scalar_field_qty = p_current_frame_->get_param_qty();

    for (size_t i = 0; i < scalar_field_qty; ++i)
    {
      Scalar_field::SP p_scalar_field = p_current_frame_->get_param(i);

      if (p_scalar_field->get_name() == name)
        return vertices_color_[i];
    }

    static IMesh::t_container_color empty;
    return empty;
  }
  
  const IMesh::t_container_color &Mesh::get_elements_color(IMesh::element_type type, const QString &name) const
  {
    const size_t scalar_field_qty = p_current_frame_->get_element_param_qty(type);

    for (size_t i = 0; i < scalar_field_qty; ++i)
    {
      Scalar_field::SP p_scalar_field = p_current_frame_->get_element_param(type, i);

      if (p_scalar_field->get_name() == name)
        return elements_color_[type][i];
    }
    
    static IMesh::t_container_color empty;
    return empty;
  }

  const IMesh::t_container_element &Mesh::get_nodes(IMesh::element_type element) const
  {
    return p_current_frame_->get_geometry()->get_nodes(element);
  }

  //
  // Mesh::Geometry
  //
  Mesh::Geometry::Geometry(const IMesh::t_container_vertex &vertices, const QMap<IMesh::element_type, IMesh::t_container_element> &node_map):
    vertices_(vertices),
    node_map_(node_map)
  {
  }

  size_t Mesh::Geometry::get_vertex_qty() const
  {
    return vertices_.size();
  }
  const IMesh::t_container_vertex &Mesh::Geometry::get_vertices() const
  {
    return vertices_;
  }

  const IMesh::t_container_element &Mesh::Geometry::get_nodes(IMesh::element_type element) const
  {
    return node_map_[element];
  }

  //
  // Mesh::Scalar_field
  //
  Mesh::Scalar_field::Scalar_field(const QString &name, const QVector<param_t> &param):
    name_(name),
    values_(param),
    min_(*std::min_element(param.begin(), param.end())),
    max_(*std::max_element(param.begin(), param.end()))
  {
  }
  
  void Mesh::Scalar_field::recalc()
  {
    min_ = *std::min_element(values_.begin(), values_.end());
    max_ = *std::max_element(values_.begin(), values_.end());
  }


  QString Mesh::Scalar_field::get_name() const
  {
    return name_;
  }

  QVector<Mesh::Scalar_field::param_t> &Mesh::Scalar_field::get_values()
  {
    return values_;
  }
  
  const QVector<Mesh::Scalar_field::param_t> &Mesh::Scalar_field::get_values() const
  {
    return values_;
  }

  void Mesh::copy_frames(Mesh::Frame_reader *p_reader, Mesh::Frame_writer *p_writer)
  {
    while (Frame::SP p_frame = p_reader->read())
      p_writer->write(p_frame);
  }
} // namespace models {